
#include <BReadWrite>
#include <BOpenGL>
#include <BImage>
#include "member_BImage.h"
#include "member_BTexture.h"

using namespace BWE;

#define member					(*(member_BImage*)_ptr)
#define member_allocate()		_ptr = new member_BImage(this)
#define member_release()		delete (member_BImage*)_ptr

BImage::BImage()
{
	member_allocate();
}
BImage::BImage(const BString& fileName)
{
	member_allocate();
	if (ReadFile(fileName, this))
	{
		member.fileName = fileName;
	}
}
BImage::BImage(Format format)
{
	member_allocate();
	setFormat(format);
}
BImage::BImage(const BSize& size, Format format)
{
	member_allocate();
	setFormat(format);
	setSize(size);
}
BImage::BImage(int width, int height, Format format)
{
	member_allocate();
	setFormat(format);
	setSize(width, height);
}
BImage::BImage(const BImage& other)
{
	member_allocate();
	setBlend(other.blend());
	setFilter(other.filter());
	setWrap(other.wrap());
	setPolicy(other.policy());
	setFormat(other.format());
	setSize(other.size());
	setPixels(*other.pixels());
}
BImage::~BImage()
{
	member_release();
}

void BImage::copy(const BTexture* other)
{
	BTexture::copy(other);
	if (const BImage* image = dynamic_cast<const BImage*>(other))
	{
		member.fileName = image_member(image)->fileName;
		member.policy = image_member(image)->policy;
		member.format = image_member(image)->format;
		member.fmtlen = image_member(image)->fmtlen;
		member.size = image_member(image)->size;
		setPixels(image_member(image)->pixels);
	}
}
BImage* BImage::clone() const
{
	BImage* image = new BImage();
	image->copy(this);
	return image;
}

void BImage::setFileName(const BString& fileName)
{
	if (member.fileName != fileName)
	{
		member.fileName = fileName;
		if (this->checks(Signal_FileNameChanged))
			this->emit(Signal_FileNameChanged, fileName);
	}
}
const BString& BImage::fileName() const
{
	return member.fileName;
}

void BImage::setPolicy(Policy policy)
{
	if (member.policy != policy)
	{
		member.policy = policy;
		emit(Signal_Changed);
	}
}
Policy BImage::policy() const
{
	return member.policy;
}

void BImage::setFormat(Format format)
{
	if (member.format != format)
	{
		member.format = format;
		switch (format)
		{
		case Format_RGB:
			member.fmtlen = 3;
			break;
		case Format_RGBA:
			member.fmtlen = 4;
			break;
		case Format_Alpha:
			member.fmtlen = 1;
			break;
		case Format_Gray:
			member.fmtlen = 1;
			break;
		case Format_Gray_Alpha:
			member.fmtlen = 2;
			break;
		case Format_Char:
			member.fmtlen = 1;
			break;
		case Format_Short:
			member.fmtlen = 2;
			break;
		case Format_UChar:
			member.fmtlen = 1;
			break;
		case Format_UShort:
			member.fmtlen = 2;
			break;
		case Format_Int:
		case Format_UInt:
		case Format_Float:
			member.fmtlen = 4;
			break;
		case Format_Double:
			member.fmtlen = 8;
			break;
		default:
			member.format = Format_None;
			member.fmtlen = 0;
			break;
		}
		member.resizePixels();
		this->dirty();
	}
}
Format BImage::format() const
{
	return member.format;
}

bool BImage::setWidth(int width)
{
	BSize size(width, member.size.height());
	return setSize(size);
}
int BImage::width() const
{
	return member.size.width();
}

bool BImage::setHeight(int height)
{
	BSize size(member.size.width(), height);
	return setSize(size);
}
int BImage::height() const
{
	return member.size.height();
}

bool BImage::setSize(int width, int height)
{
	return setSize(BSize(width, height));
}
bool BImage::setSize(const BSize& size)
{
	if (member.size != size)
	{
		member.size = size;
		member.resizePixels();
		this->dirty();
		return true;
	}
	return false;
}
const BSize& BImage::size() const
{
	return member.size;
}

bool BImage::setPixels(const BByteArray& pixels)
{
	if (member.pixels->size() == pixels.size())
	{
		(*member.pixels) = pixels;
		this->dirty();
	}
	return true;
}
void BImage::setPixels(BByteArray* pixels)
{
	if (member.pixels != pixels)
	{
		member.pixels = pixels;
		this->dirty();
	}
}
BByteArray* BImage::pixels()
{
	return member.pixels;
}
const BByteArray* BImage::pixels() const
{
	return member.pixels;
}

int BImage::fmtlen() const
{
	return member.fmtlen;
}
int BImage::rowlen() const
{
	return member.fmtlen * member.size.width();
}

BByte* BImage::bytes(int row)
{
	if (member.pixels.empty())
		return 0;
	BByte* data = member.pixels->data();
	return data + member.fmtlen * member.size.width() * row;
}
const BByte* BImage::bytes(int row) const
{
	if (member.pixels.empty())
		return 0;
	const BByte* data = member.pixels->data();
	return data + member.fmtlen * member.size.width() * row;
}

bool BImage::setPixelColor(int x, int y, const BColor& color)
{
	if (member.pixels.empty())
		return false;
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	BByte* data = member.pixels->data() + member.fmtlen * (member.size.width() * y + x);
	switch (member.format)
	{
	case Format_RGBA:
		data[0] = color.r();
		data[1] = color.g();
		data[2] = color.b();
		data[3] = color.a();
		break;
	case Format_RGB:
		data[0] = color.r();
		data[1] = color.g();
		data[2] = color.b();
		break;
	case Format_Alpha:
		data[0] = color.a();
		break;
	case Format_Gray:
		data[0] = color.gray();
		break;
	case Format_Gray_Alpha:
		data[0] = color.gray();
		data[1] = color.a();
		break;
	default:
		return false;
	}
	this->dirty();
	return true;
}
bool BImage::pixelColor(int x, int y, BColor& color) const
{
	if (member.pixels.empty())
		return false;
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	const BByte* data = member.pixels->data() + member.fmtlen * (member.size.width() * y + x);
	switch (member.format)
	{
	case Format_RGBA:
		color.r() = data[0];
		color.g() = data[1];
		color.b() = data[2];
		color.a() = data[3];
		break;
	case Format_RGB:
		color.r() = data[0];
		color.g() = data[1];
		color.b() = data[2];
		break;
	case Format_Alpha:
		color.a() = data[0];
		break;
	case Format_Gray:
		color.r() = data[0];
		color.g() = data[0];
		color.b() = data[0];
		break;
	case Format_Gray_Alpha:
		color.r() = data[0];
		color.g() = data[0];
		color.b() = data[0];
		color.a() = data[1];
		break;
	default:
		return false;
	}
	return true;
}
BColor BImage::pixelColor(int x, int y) const
{
	BColor color;
	pixelColor(x, y, color);
	return color;
}

bool BImage::paste(const BRect& rect, const BImage* image)
{
	return paste(rect.x(), rect.y(), rect.width(), rect.height(), image);
}
bool BImage::paste(int x, int y, int width, int height, const BImage* image)
{
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	if (x + width >= member.size.width())
		width = member.size.width() - x;
	if (width < 1)
		return false;
	if (y + height >= member.size.height())
		height = member.size.height() - y;
	if (height < 1)
		return false;
	for (int r = 0; r < height; r++)
	{
		BByte* ptr0 = this->bytes(y + r);
		const BByte* ptr1 = image->bytes(r);
		memcpy(ptr0, ptr1, image->rowlen());
	}
	this->dirty();
	return true;
}

bool BImage::clone(const BRect& rect, BImage* image) const
{
	return clone(rect.x(), rect.y(), rect.width(), rect.height(), image);
}
bool BImage::clone(int x, int y, int width, int height, BImage* image) const
{
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	if (x + width >= member.size.width())
		width = member.size.width() - x;
	if (width < 1)
		return false;
	if (y + height >= member.size.height())
		height = member.size.height() - y;
	if (height < 1)
		return false;
	if (image->format() == Format_None)
		image->setFormat(member.format);
	if (image->format() != member.format)
		return false;
	image->setSize(width, height);
	int xoffset = x * member.fmtlen;
	for (int r = 0; r < height; r++)
	{
		BByte* ptr0 = image->bytes(r);
		const BByte* ptr1 = this->bytes(y + r) + xoffset;
		memcpy(ptr0, ptr1, image->rowlen());
	}
	return true;
}

void BImage::clear()
{
	member.format = Format_None;
	member.size.set(0, 0);
	member.pixels = 0;
	this->dirty();
}
bool BImage::empty() const
{
	if (member.size.width() * member.size.height() <= 0)
		return true;
	if (member.format == Format_None)
		return true;
	if (member.pixels == 0)
		return true;
	return false;
}

bool BImage::roll(int direct)
{
	if (direct < 0)
	{
		int fmtlen = member.fmtlen;
		BByte* data = member.pixels->data();
		BSize size(member.size.height(), member.size.width());
		BByteArray* pixels = new BByteArray(member.pixels->size());
		for (int r = 0; r < member.size.height(); r++)
		{
			for (int c = 0; c < member.size.width(); c++)
			{
				BByte* data0 = member.pixels->data((r * member.size.width() + c) * fmtlen);
				int row = (size.height() - c - 1);
				BByte* data1 = pixels->data((row * size.width() + r) * fmtlen);
				memcpy(data1, data0, fmtlen);
			}
		}
		member.size = size;
		member.pixels = pixels;
		this->dirty();
		return true;
	}
	if (direct > 0)
	{
		int fmtlen = member.fmtlen;
		BByte* data = member.pixels->data();
		BSize size(member.size.height(), member.size.width());
		BByteArray* pixels = new BByteArray(member.pixels->size());
		for (int r = 0; r < member.size.height(); r++)
		{
			for (int c = 0; c < member.size.width(); c++)
			{
				BByte* data0 = member.pixels->data((r * member.size.width() + c) * fmtlen);
				int column = (size.width() - r - 1);
				BByte* data1 = pixels->data((c * size.width() + column) * fmtlen);
				memcpy(data1, data0, fmtlen);
			}
		}
		member.size = size;
		member.pixels = pixels;
		this->dirty();
		return true;
	}
	return false;
}
bool BImage::inverse(bool alpha)
{
	if (member.pixels.empty())
		return false;
	int fmtlen = member.fmtlen;
	BByte* data = member.pixels->data();
	int size = member.pixels->size();
	if (member.format == Format_RGBA)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			bytes[1] = 255 - bytes[1];
			bytes[2] = 255 - bytes[2];
			if (alpha)
				bytes[3] = 255 - bytes[3];
			pos += 4;
		}
		this->dirty();
		return true;
	}
	if (member.format == Format_RGB)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			bytes[1] = 255 - bytes[1];
			bytes[2] = 255 - bytes[2];
			pos += 3;
		}
		this->dirty();
		return true;
	}
	if (member.format == Format_Gray)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			pos += 1;
		}
		this->dirty();
		return true;
	}
	if (member.format == Format_Gray && alpha)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			pos += 1;
		}
		this->dirty();
		return true;
	}
	if (member.format == Format_Gray_Alpha)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			if (alpha)
				bytes[1] = 255 - bytes[1];
			pos += 2;
		}
		this->dirty();
		return true;
	}
	return false;
}
bool BImage::reverse(Orientation orientation)
{
	if (member.pixels.empty())
		return false;
	BByte* data = member.pixels->data();
	int rowlen = member.fmtlen * member.size.width();
	int fmtlen = member.fmtlen;
	bool dirty = false;
	if (orientation & Orientation_Vertical)
	{
		int r0 = 0;
		int r1 = member.size.height() - 1;
		while (r0 < r1)
		{
			BByte* ptr0 = data + r0 * rowlen;
			BByte* ptr1 = data + r1 * rowlen;
			bSwap(ptr0, ptr1, rowlen);
			r0++;
			r1--;
		}
		dirty = true;
	}
	if (orientation & Orientation_Horizontal)
	{
		for (int r = 0; r < member.size.height(); r++)
		{
			BByte* ptr = data + r * rowlen;
			int c0 = 0;
			int c1 = member.size.width() - 1;
			while (c0 < c1)
			{
				bSwap(ptr + c0 * fmtlen, ptr + c1 * fmtlen, fmtlen);
				c0++;
				c1--;
			}
		}
		dirty = true;
	}
	if (dirty)
		this->dirty();
	return true;
}

void BImage::dirty() const
{
	BTexture::dirty();
	if (!member.dirty)
	{
		member.dirty = true;
		emit(Signal_Dirty);
	}
}
bool BImage::flush() const
{
	BTexture::flush();

	if (texture_member(this)->tbo == 0)
		return false;

	GLuint tbo = texture_member(this)->tbo;

	if (member.dirty && member.pixels)
	{
		int size = member.pixels->size();
		int w = member.size.width();
		int h = member.size.height();

		if (size != w * h * member.fmtlen)
			return false;

		GLvoid* data = (GLvoid*)member.pixels->data();

		glBindTexture(GL_TEXTURE_2D, tbo);
		switch (member.format)
		{
		case Format_Char:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_BYTE, data);
			break;
		case Format_Alpha:
		case Format_UChar:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Short:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_SHORT, data);
			break;
		case Format_UShort:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_SHORT, data);
			break;
		case Format_Int:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_INT, data);
			break;
		case Format_UInt:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_INT, data);
			break;
		case Format_Float:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_FLOAT, data);
			break;
		case Format_Double:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_DOUBLE, data);
			break;
		case Format_RGBA:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_RGB:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Gray:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Gray_Alpha:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Compressed_Alpha:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_ALPHA, w, h, 0, size, data);
			break;
		case Format_Compressed_RGBA:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, w, h, 0, size, data);
			break;
		case Format_Compressed_RGB:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, w, h, 0, size, data);
			break;
		case Format_Compressed_Gray:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_LUMINANCE, w, h, 0, size, data);
			break;
		default:
			break;
		}
		if (texture_member(this)->mipmap)
		{
			glGenerateMipmap(GL_TEXTURE_2D);
		}
		if (member.policy == Policy_Fixed)
			member.pixels = 0;
		member.dirty = false;
	}
	glBindTexture(GL_TEXTURE_2D, tbo);
	return true;
}

const BImage& BImage::operator = (const BImage& other)
{
	if (this != &other)
	{
		setBlend(other.blend());
		setFilter(other.filter());
		setWrap(other.wrap());
		setPolicy(other.policy());

		setFormat(other.format());
		setSize(other.size());
		setPixels(*other.pixels());
	}
	return *this;
}
